#include "OgreStableHeaders.h"
#include "OgreFileSystem.h"
#include "OgreLogManager.h"
#include "OgreException.h"
#include "OgreStringVector.h"
#include "OgreRoot.h"

#include <sys/types.h>
#include <sys/stat.h>

#if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE || \
    OGRE_PLATFORM == OGRE_PLATFORM_SYMBIAN || OGRE_PLATFORM == OGRE_PLATFORM_IPHONE || \
	OGRE_PLATFORM == OGRE_PLATFORM_ANDROID || OGRE_PLATFORM == OGRE_PLATFORM_TEGRA2
#include "OgreSearchOps.h"
#include <sys/param.h>
#define MAX_PATH MAXPATHLEN
#endif

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#if !defined(NOMINMAX) && defined(_MSC_VER)
#define NOMINMAX
#endif
#include <windows.h>
#include <direct.h>
#include <io.h>
#endif

namespace Ogre
{
	bool FileSystemArchive::ms_IgnoreHidden = true;

	FileSystemArchive::FileSystemArchive(const String& name, const String& archType)
		: Archive(name, archType)
	{
	}

	bool FileSystemArchive::isCaseSensitive(void) const
	{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
		return false;
#else
		return true;
#endif
	}

	static bool is_reserved_dir(const char* fn)
	{
		return (fn[0] == '.' && (fn[1] == 0 || (fn[1] == '.' && fn[2] == 0)));
	}

	static bool is_absolute_path(const char* path)
	{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
		if (isalpha(uchar(path[0])) && path[1] == ':')
			return true;
#else
		return path[0] == '/' || path[0] == '\\';
#endif
	}

	static String concatenate_path(const String& base, const String& name)
	{
		if (base.empty() || is_absolute_path(name.c_str()))
			return name;
		else
			return base + '/' + name;
	}

	void FileSystemArchive::findFiles(const String& pattern, bool recursive,
		bool dirs, StringVector* simpleList, FileInfoList* detailList)
	{
		long lHandle, res;
		struct _finddata_t tagData;

		size_t pos1 = pattern.rfind('/');
		size_t pos2 = pattern.rfind('\\');
		if (pos1 == pattern.npos || ((pos2 != pattern.npos) && (pos1 < pos2)))
			pos1 = pos2;
		String directory;
		if (pos1 != pattern.npos)
			directory = pattern.substr(0, pos1 + 1);

		String full_pattern = concatenate_path(mName, pattern);

		lHandle = _findfirst(full_pattern.c_str(), &tagData);
		res = 0;
		while (lHandle != -1 && res != -1)
		{
			if ((dirs == ((tagData.attrib & _A_SUBDIR) != 0)) &&
				(!ms_IgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0) &&
				(!dirs || !is_reserved_dir(tagData.name)))
			{
				if (simpleList)
				{
					simpleList->push_back(directory + tagData.name);
				}
				else if (detailList)
				{
					FileInfo fi;
					fi.archive = this;
					fi.filename = directory + tagData.name;
					fi.basename = tagData.name;
					fi.path = directory;
					fi.compressedSize = tagData.size;
					fi.uncompressedSize = tagData.size;
					detailList->push_back(fi);
				}
			}
			res = _findnext(lHandle, &tagData);
		}
		if (lHandle != -1)
			_findClose(lHandle);

		if (recursive)
		{
			String base_dir = mName;
			if (!directory.empty())
			{
				base_dir = concatenate_path(mName, directory);
				base_dir.erase(base_dir.length() - 1);
			}
			base_dir.append("/*");

			String mask("/");
			if (pos1 != pattern.npos)
				mask.append(pattern.substr(pos1 + 1));
			else
				mask.append(pattern);

			lHandle = _findfirst(base_dir.c_str(), &tagData);
			res = 0;
			while (lHandle != -1 && res != -1)
			{
				if ((tagData.attrib & _A_SUBDIR) &&
					(!ms_IgnoreHidden || (tagData.attrib & _A_HIDDEN) == 0) &&
					!is_reserved_dir(tagData.name))
				{
					base_dir = directory;
					base_dir.append(tagData.name).append(mask);
					findFiles(base_dir, recursive, dirs, simpleList, detailList);
				}
				res = _findnext(lHandle, &tagData);
			}

			if (lHandle != -1)
				_findclose(lHandle);
		}
	}

	FileSystemArchive::~FileSystemArchive()
	{
		unload();
	}

	void FileSystemArchive::load()
	{
		OGRE_LOCK_AUTO_MUTEX
		String testPath = concatenate_path(mName, "__testwrite.ogre");
		std::ofstream writeStream;
		writeStream.open(testPath.c_str());
		if (writeStream.fail())
			mReadOnly = true;
		else
		{
			mReadOnly = false;
			writeStream.close();
			::remove(testPath.c_str());
		}
	}

	void FileSystemArchive::unload()
	{

	}

	DataStreamPtr FileSystemArchive::open(const String& filename, bool readOnly) const
	{
		String full_path = concatenate_path(mName, filename);

		struct stat tagStat;
		int ret = stat(full_path.c_str(), &tagStat);
		assert(ret == 0 && "Program getting file size");
		(void)ret;

		std::ios::openmode mode = std::ios::in | std::ios::binary;
		std::istream* baseStream = 0;
		std::ifstream* roStream = 0;
		std::fstream* rwStream = 0;

		if (!readOnly && isReadOnly())
		{
			mode |= std::ios::out;
			rwStream = OGRE_NEW_T(std::fstream, MEMCATEGORY_GENERAL)();
			rwStream->open(full_path.c_str(), mode);
			baseStream = rwStream;
		}
		else
		{
			roStream = OGRE_NEW_T(std::ifstream, MEMCATEGORY_GENERAL)();
			roStream->open(full_path.c_str(), mode);
			baseStream = roStream;
		}

		if (baseStream->fail())
		{
			OGRE_DELETE_T(roStream, basic_ifstream, MEMCATEGORY_GENERAL);
			OGRE_DELETE_T(rwStream, basic_fstream, MEMCATEGORY_GENERAL);
			OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND,
				"Cannot open file: " + filename,
				"FileSystemArchive::open");
		}

		FileStreamDataStream* stream = 0;
		if (rwStream)
		{
			stream = OGRE_NEW FileStreamDataStream(filename,
				rwStream, (size_t)tagStat.st_size, true);
		}
		else
		{
			stream = OGRE_NEW FileStreamDataStream(filename,
				roStream, (size_t)tagStat.st_size, true);
		}
		return DataStreamPtr(stream);
	}

	DataStreamPtr FileSystemArchive::create(const String& filename) const
	{
		if (isReadOnly())
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
				"Cannot create a file in a read-only archive",
				"FileSystemArchive::remove");
		}

		String full_path = concatenate_path(mName, filename);
		std::ios::openmode mode = std::ios::out | std::ios::binary;
		std::fstream& rwStream = OGRE_NEW_T(std::fstream, MEMCATEGORY_GENERAL)();
		rwStream->open(full_path.c_str(), mode);

		if (rwStream->fail())
		{
			OGRE_DELETE_T(rwStream, basic_fstream, MEMCATEGORY_GENERAL);
			OGRE_EXCEPT(Exception::ERR_FILE_NOT_FOUND,
				"Cannot open file: " + filename,
				"FileSystemArchive::create");
		}

		FileStreamDataStream* stream = OGRE_NEW FileStreamDataStream(filename,
			rwStream, 0, true);
		return DataStreamPtr(stream);
	}

	void FileSystemArchive::remove(const String& filename) const
	{
		if (isReadOnly())
		{
			OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
				"Cannot remove a file from a read-only archive",
				"FileSystemArchive::remove");
		}
		String full_path = concatenate_path(mName, filename);
		::remove(full_path.c_str());
	}

	StringVectorPtr FileSystemArchive::list(bool recursive, bool dirs)
	{
		StringVectorPtr ret(OGRE_NEW_T(StringVector, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

		findFiles("*", recursive, dirs, ret.getPointer(), 0);

		return ret;
	}

	FileInfoListPtr FileSystemArchive::listFileInfo(bool recursive, bool dirs)
	{
		FileInfoListPtr ret(OGRE_NEW_T(FileInfoList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

		findFiles("*", recursive, dirs, 0, ret.getPointer());

		return ret;
	}

	StringVectorPtr FileSystemArchive::find(const String& pattern,
											bool recursive, bool dirs)
	{
		StringVectorPtr ret(OGRE_NEW_T(StringVector, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

		findFiles(pattern, recursive, dirs, ret.getPointer(), 0);

		return ret;
	}

	FileInfoListPtr FileSystemArchive::findFileInfo(const String& pattern,
		bool recursive, bool dirs)
	{
		FileInfoListPtr ret(OGRE_NEW_T(FileInfoList, MEMCATEGORY_GENERAL)(), SPFM_DELETE_T);

		findFiles(pattern, recursive, dirs, 0, ret.getPointer());

		return ret;
	}

	bool FileSystemArchive::exists(const String& filename)
	{
		String full_path = concatenate_path(mName, filename);

		struct stat tagStat;
		bool ret = (stat(full_path.c_str(), &tagStat) == 0);

		if (ret && is_absolute_path(filename.c_str()))
		{
#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
			String lowerCaseName = mName;
			StringUtil::toLowerCase(lowerCaseName);
			ret = Ogre::StringUtil::startsWith(full_path, lowerCaseName, true);
#else
			ret = Ogre::StringUtil::startsWith(full_path, mName, false);
#endif
		}

		return ret;
	}

	time_t FileSystemArchive::getModifiedTime(const String& filename)
	{
		String full_path = concatenate_path(mName, filename);

		struct stat tagStat;
		bool ret = (stat(full_path.c_str(), &tagStat) == 0);

		if (ret)
		{
			return tagStat.st_mtime;
		}
		else
		{
			return 0;
		}
	}

	const String& FileSystemArchiveFactory::getType(void) const
	{
		static String name = "FileSystem";
		return name;
	}
}
